package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.HttpClientUtil;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.order.mapper.OrderDetailMapper;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper,OrderInfo> implements OrderService {
    //  调用mapper 层。
    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Value("${ware.url}")
    private String wareUrl; //  http://localhost:9001

    @Override
    public String getTradeNo(String userId) {
        //  生成流水号。
        String tradeNo = UUID.randomUUID().toString();

        //  写入缓存中.
        String tradeNoKey = "tradeNO:"+userId;
        this.redisTemplate.opsForValue().set(tradeNoKey,tradeNo);
        //  返回流水号。
        return tradeNo;
    }

    /**
     * 比较流水号
     * @param userId    组成缓存key，来获取缓存流水号
     * @param tradeNo   前端传递过来的流水号.
     * @return
     */
    @Override
    public Boolean checkTradeNo(String userId, String tradeNo) {
        //  组成缓存的key
        String tradeNoKey = "tradeNO:"+userId;
        //  获取缓存数据
        String tradeNoRedis = (String) this.redisTemplate.opsForValue().get(tradeNoKey);
        //  返回比较结果。
        return tradeNo.equals(tradeNoRedis);
    }

    @Override
    public OrderInfo getOrderInfo(Long orderId) {
        //  select * from order_info where id = orderId;
        //  select * from order_detail where order_id = orderId;
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);

        if (orderInfo!=null){
            QueryWrapper<OrderDetail> orderDetailQueryWrapper = new QueryWrapper<>();
            orderDetailQueryWrapper.eq("order_id",orderId);
            List<OrderDetail> orderDetails = orderDetailMapper.selectList(orderDetailQueryWrapper);
            orderInfo.setOrderDetailList(orderDetails);
        }
        //  返回数据.
        return orderInfo;
    }

    @Override
    public void execExpiredOrder(Long orderId) {
        //  取消订单的本质：更新订单的状态为CLOSED;
        //  orderId = order_info.id; id 主键;
        //  后续业务会有很多根据订单Id 来更新状态的需求。因此，做一个方法抽离.
        //  this.updateOrderStatus(orderId,ProcessStatus.PAID);
        this.updateOrderStatus(orderId,ProcessStatus.CLOSED);

        //  关闭 paymentInfo  sendMsg();
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE,MqConst.ROUTING_PAYMENT_CLOSE,orderId);
    }

    @Override
    public void sendOrderStatus(Long orderId) {
        //  发送消息，给库存，减库存。
        this.updateOrderStatus(orderId,ProcessStatus.NOTIFIED_WARE);
        //  准备发送消息的字符串。
        String wareJson = initWareOrder(orderId);

        //  发送消息给库存系统.
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_WARE_STOCK,MqConst.ROUTING_WARE_STOCK,wareJson);

    }

    //  创建发送消息给仓库的内容。
    private String initWareOrder(Long orderId) {
        //  json 字符串是由orderInfo 的部分字段组成。
        OrderInfo orderInfo = this.getOrderInfo(orderId);
        //  将orderInfo--> map 集合。
        Map map = this.initWareOrder(orderInfo);
        return JSON.toJSONString(map);
    }

    @Override
    public void execExpiredOrder(Long orderId, String flag) {
        //  this.updateOrderStatus(orderId,ProcessStatus.PAID);
        this.updateOrderStatus(orderId,ProcessStatus.CLOSED);
        //  当orderInfo  和 paymentInfo 都有记录的时候， 需要让flag = 2;
        if ("2".equals(flag)){
            //  关闭 paymentInfo  sendMsg();
            this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE,MqConst.ROUTING_PAYMENT_CLOSE,orderId);
        }
    }

    @Override
    public List<OrderInfo> orderSplit(String orderId, String wareSkuMap) {
        /*
        拆单业务：
            1.  先获取到原始订单 :
            2.  将 wareSkuMap 变为能操作的对象. [{"wareId":"1","skuIds":["24"]},{"wareId":"2","skuIds":["23"]}]
            3.  创建新的子订单 并给子订单进行赋值
            4.  将子订单添加到子订单集合中
            5.  保存新的子订单
            6.  更新原始订单状态
         */
        //  创建子订单集合对象
        ArrayList<OrderInfo> subOrderDetailList = new ArrayList<>();
        OrderInfo orderInfoOrigin = this.getOrderInfo(Long.parseLong(orderId));
        //  将 wareSkuMap 变为能操作的对象
        List<Map> mapList = JSON.parseArray(wareSkuMap, Map.class);
        //  循环遍历这个集合
        for (Map map : mapList) {
            String wareId = (String) map.get("wareId");
            //  仓库Id 下有可能会有很多商品Id
            List<String> skuIdList = (List<String>) map.get("skuIds");
            //  创建子订单
            OrderInfo subOrderInfo = new OrderInfo();

            BeanUtils.copyProperties(orderInfoOrigin,subOrderInfo);
            subOrderInfo.setId(null);
            //  子订单的父Id
            subOrderInfo.setParentOrderId(Long.parseLong(orderId));
            //  赋值一个仓库Id
            subOrderInfo.setWareId(wareId);
            //  重新计算子订单的金额。
            //  总价格： 单价*数量; ==>  subOrderInfo.sumTotalAmount();
            //  需要找到子订单明细，并给子订单进行赋值.
            //  声明一个子订单明细集合。
            ArrayList<OrderDetail> subOrderDetails = new ArrayList<>();
            //  能够找到 skuId = 23,24
            List<OrderDetail> orderDetailList = orderInfoOrigin.getOrderDetailList();
            for (OrderDetail orderDetail : orderDetailList) {
                //  循环json 字符串中获取到的对应skuId
                for (String skuId : skuIdList) {
                    if (orderDetail.getSkuId().longValue() == Long.parseLong(skuId)){
                        //  找到了订单明细
                        subOrderDetails.add(orderDetail);
                    }
                }
            }
            subOrderInfo.setOrderDetailList(subOrderDetails);
            subOrderInfo.sumTotalAmount();

            //  将子订单添加到集合中.
            subOrderDetailList.add(subOrderInfo);
            //  将子订单保存到数据库中.
            this.saveOrderInfo(subOrderInfo);
        }
        //  将原始订单状态变为分割SPLIT
        this.updateOrderStatus(Long.parseLong(orderId),ProcessStatus.SPLIT);
        //  返回子订单集合.
        return subOrderDetailList;
    }

    /**
     * 将 orderInfo--> map 集合。
     * @param orderInfo
     * @return
     */
    public Map initWareOrder(OrderInfo orderInfo) {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("orderId",orderInfo.getId());
        hashMap.put("consignee",orderInfo.getConsignee());
        hashMap.put("consigneeTel",orderInfo.getConsigneeTel());
        hashMap.put("orderComment",orderInfo.getOrderComment());
        hashMap.put("orderBody",orderInfo.getTradeBody());
        hashMap.put("deliveryAddress",orderInfo.getDeliveryAddress());
        hashMap.put("paymentWay","2");
        //  添加一个字段：wareId
        hashMap.put("wareId",orderInfo.getWareId());
        //  上面字段都是 orderInfo的.
        //  声明一个集合对象 : List<Map>

        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        // List<POJO>
        List<HashMap<String, Object>> mapList = orderDetailList.stream().map(orderDetail -> {
            HashMap<String, Object> detailHashMap = new HashMap<>();
            detailHashMap.put("skuId", orderDetail.getSkuId());
            detailHashMap.put("skuNum", orderDetail.getSkuNum());
            detailHashMap.put("skuName", orderDetail.getSkuName());
            return detailHashMap;
        }).collect(Collectors.toList());
        //  details 是属于订单明细的.
        hashMap.put("details",mapList);

        return hashMap;
    }

    /**
     * 更新订单的方法。
     * @param orderId
     * @param processStatus
     */
    public void updateOrderStatus(Long orderId,ProcessStatus processStatus){
        //  取消订单的本质：更新订单的状态为CLOSED;
        //  orderId = order_info.id; id 主键;
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        // orderInfo.setOrderStatus(OrderStatus.CLOSED.name());
        // processStatus.getOrderStatus() = OrderStatus.CLOSED
        orderInfo.setOrderStatus(processStatus.getOrderStatus().name());
        // orderInfo.setProcessStatus(ProcessStatus.CLOSED.name());
        orderInfo.setProcessStatus(processStatus.name());
        orderInfoMapper.updateById(orderInfo);
    }


    //  查看我的订单列表功能.
    @Override
    public IPage<OrderInfo> getOrderPageList(Page<OrderInfo> orderInfoPage, String userId) {
        //  看谁的订单列表. 第一种： 写多表关联查询.  第二种：可以使用mapper 自己单独查询.
        IPage<OrderInfo> orderInfoIPage = orderInfoMapper.selectOrderPageList(orderInfoPage,userId);

        //  给orderInfo 对象中 orderStatusName 赋值.
        orderInfoIPage.getRecords().forEach(orderInfo -> {
            //  订单的状态是由枚举生成的.
            //  根据order_info.order_status 中的数据 ,找 OrderStatus的 备注.
            orderInfo.setOrderStatusName(OrderStatus.getStatusNameByStatus(orderInfo.getOrderStatus()));
        });

        //  基本的思想.
        return orderInfoIPage;
    }

    @Override
    public Boolean checkStock(Long skuId, Integer skuNum) {
        //  远程调用.  http://localhost:9001/hasStock?skuId=10221&num=2
        /*
        ware:
            url: http://localhost:9001
         */
        //  没有spring cloud , 在此使用httpClient 工具类.
        String result = HttpClientUtil.doGet(wareUrl + "/hasStock?skuId=" + skuId + "&num=" + skuNum);
        //  返回结果  1表示有库存， 0表示没有库存.
        return "1".equals(result);
    }

    @Override
    public void delTradeNo(String userId) {
        //  组成缓存的key
        String tradeNoKey = "tradeNO:"+userId;

        this.redisTemplate.delete(tradeNoKey);
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 开启事务.
    public Long saveOrderInfo(OrderInfo orderInfo) {
        //  看页面哪些字段没有封装数据，有必要的必须要自己在后台赋值，没有必要的，就不用管了。
        //  total_amount,order_status,user_id{ 控制层赋值。} out_trade_no,trade_body,operate_time,expire_time,process_status
        //  单价 * 数量 = orderInfo
        //  赋值总金额；
        //  计算总金额中 包含了  orderInfo.setTotalAmount(); 赋值。
        //  sumTotalAmount(); 方法要计算成功，必须保证 orderInfo 中有订单明细集合。 我们传递数据的时候，已经将订单明细封装进去了。
        //  orderInfo.getOrderDetailList(); 是有数据：
        orderInfo.sumTotalAmount();
        //  订单状态. 初始化值：是未付款.
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //  第三方交易编号: 后续做支付用的！ 作用：防止订单重复支付，保证了支付的幂等性！ 不能重复；
        // String outTradeNo = UUID.randomUUID().toString();
        String outTradeNo = "ATGUIGU" + System.currentTimeMillis() + "" + new Random().nextInt(1000);
        orderInfo.setOutTradeNo(outTradeNo);
        //  订单的描述;
        orderInfo.setTradeBody("买一双大头鞋.手机");
        //  如果不想给固定值，可以将每个订单明细的名称拼接在一起，赋值给 tradeBody
        //  操作时间
        orderInfo.setOperateTime(new Date());
        //  订单的过期时间 ：每个商品的对应的订单过期时间可能不一致。 1天;
        Calendar calendar = Calendar.getInstance();
        //  在当前系统时间上 +1天了
        calendar.add(Calendar.DATE,1);
        orderInfo.setExpireTime(calendar.getTime());
        //  赋值订单状态.
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        //  保存orderInfo 表；
        orderInfoMapper.insert(orderInfo);

        //  保存orderDetail 表; 获取到了页面提交过来的订单明细集合数据.
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        orderDetailList.forEach(orderDetail -> {
            //  保存订单明细；
            //  orderInfo.getId() 此时这个id 不是空。 因为插入orderInfo 代码在上面了执行了。
            //  type = IdType.AUTO 表示获取到了插入之后的主键自增.
            orderDetail.setOrderId(orderInfo.getId());
            orderDetailMapper.insert(orderDetail);
        });

        //  获取订单Id
        Long orderId = orderInfo.getId();

        //  发送延迟消息.
        //  发送的消息都是根据消费者的需求决定的.
        this.rabbitService.sendDelayMsg(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL,MqConst.ROUTING_ORDER_CANCEL,orderId,MqConst.DELAY_TIME);
        //  返回数据。
        return orderId;
    }
}
